package com.fognl.core.net.handler.request;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;

import com.fognl.core.net.handler.Request;
import com.fognl.core.net.handler.WorkerException;
import com.fognl.core.net.handler.Request.Worker;

/** An Http worker. */
public class HttpWorker implements Worker
{
	private final String TAG = "HttpWorker";
	
	private URL url;
	private ByteArrayOutputStream outputStream;
	private Map<String, String> params;
	private String encoding;
	private boolean cancelled = false;
	
	public HttpWorker(URL url, Map<String, String> params, String encoding)
	{
		super();
		this.url = url;
		this.params = params;
	}
	
	public URL getUrl() { return url; }
	public Map<String, String> getParams() { return params; }
	public String getEncoding() { return encoding; }
	
	/** Return the data read from the URL as a local <code>InputStream</code>. */
	public Object getData()
	{
		return new ByteArrayInputStream(outputStream.toByteArray());
	}
	
	public void work(Request.Worker.Listener listener) throws WorkerException
	{
		try {
			HttpClient client = new DefaultHttpClient();
			
			String urlString = getUrl().toExternalForm();
			Map<String, String> params = getParams();
			
			if(params != null && !params.isEmpty())
			{
				urlString += toParamString(params);
			}
			
			HttpUriRequest get = new HttpGet(urlString);
			
			get.setHeader("Accept-encoding", "gzip");
			
			HttpResponse response = client.execute(get);
			
			try {
				if(matches(new int[] {
						HttpStatus.SC_BAD_GATEWAY
					, HttpStatus.SC_BAD_REQUEST
					, HttpStatus.SC_FORBIDDEN
					, HttpStatus.SC_GONE
					, HttpStatus.SC_INTERNAL_SERVER_ERROR
					, HttpStatus.SC_NOT_FOUND
					// TODO: Other codes
					}, response.getStatusLine().getStatusCode()))
						throw new WorkerException(
								"Error reading data from " + getUrl().toExternalForm() + ": " + response.getStatusLine());
					
				// Is the data gzipped?
				Header encoding = response.getLastHeader("Content-Encoding");
				boolean isGzip = (
					 encoding != null 
				&& encoding.getValue() != null
				&& encoding.getValue().equalsIgnoreCase("gzip"));
				
				InputStream baseIn = response.getEntity().getContent();
				
				// Open the right kind of stream
				InputStream in = (isGzip)? new GZIPInputStream(baseIn, 2048):	baseIn;
						
				try {
					this.outputStream = new ByteArrayOutputStream();
					byte[] buf = new byte[8192];
					
					for(int read = in.read(buf); read != -1; read = in.read(buf))
					{
						if(!listener.okToContinue(this))
							return;
						this.outputStream.write(buf, 0, read);
					}
				}
				finally {
					try {
						this.outputStream.flush();
					} catch(Exception ex) {}
					
					try {
						this.outputStream.close();
					} catch(Exception ex) {}
					
					try {
						in.close();
					} catch(Exception ex) {}
				}
			}
			finally {
				client.getConnectionManager().closeExpiredConnections();
			}
		}
		catch(Exception ex)
		{
			throw new WorkerException(ex);
		}
	}
	
	/** Build a param string based off the specified parameters. */
	protected String toParamString(Map<String, String> params) throws UnsupportedEncodingException
	{
		if(params == null || params.isEmpty())
			return null;
		
		StringBuilder sb = new StringBuilder("?");
		
		String encoding = (getEncoding() != null)? getEncoding(): HttpRequest.DEFAULT_ENCODING;
		
		for(Iterator<String> e = params.keySet().iterator(); e.hasNext();)
		{
			String key = e.next();
			sb.append(key).append("=").append(URLEncoder.encode(params.get(key), encoding));
			
			if(e.hasNext())
				sb.append("&");
		} 
		
		return sb.toString();
	}
	
	private static boolean matches(int[] codes, int code)
	{
		for(int c: codes)
		{
			if(c == code)
				return true;
		}
		return false;
	}
}
